package com.gitee.wsl.compose.ui.richtext.edit.state

import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.util.fastForEach
import com.gitee.wsl.compose.ui.richtext.edit.model.span.RichSpan
import com.gitee.wsl.compose.ui.richtext.edit.model.paragraph.RichParagraph
import com.gitee.wsl.compose.ui.richtext.edit.model.paragraph.api.ConfigurableListLevel
import com.gitee.wsl.compose.ui.richtext.edit.model.paragraph.type.DefaultParagraphType
import com.gitee.wsl.compose.ui.richtext.edit.model.paragraph.type.list.OrderedList
import com.gitee.wsl.compose.ui.richtext.edit.model.paragraph.type.list.UnorderedList


fun RichTextState.toggleUnorderedList() {
    val paragraphs = getRichParagraphListByTextRange(selection)
    if (paragraphs.isEmpty())
        return
    val isFirstParagraphUnorderedList = paragraphs.first().type is UnorderedList
    paragraphs.fastForEach { paragraph ->
        if (isFirstParagraphUnorderedList)
            removeUnorderedList(paragraph)
        else
            addUnorderedList(paragraph)
    }
}

 fun RichTextState.addUnorderedList() {
    val paragraphs = getRichParagraphListByTextRange(selection)

    paragraphs.fastForEach { paragraph ->
        addUnorderedList(paragraph)
    }
}

fun RichTextState.removeUnorderedList() {
    val paragraphs = getRichParagraphListByTextRange(selection)

    paragraphs.fastForEach { paragraph ->
        removeUnorderedList(paragraph)
    }
}

 fun RichTextState.toggleOrderedList() {
    val paragraphs = getRichParagraphListByTextRange(selection)
    if (paragraphs.isEmpty())
        return
    val isFirstParagraphOrderedList = paragraphs.first().type is OrderedList
    paragraphs.fastForEach { paragraph ->
        if (isFirstParagraphOrderedList) {
            removeOrderedList(paragraph)
        } else {
            addOrderedList(paragraph)
        }
    }
}

 fun RichTextState.addOrderedList() {
    val paragraphs = getRichParagraphListByTextRange(selection)

    paragraphs.fastForEach { paragraph ->
        addOrderedList(paragraph)
    }
}

 fun RichTextState.removeOrderedList() {
    val paragraphs = getRichParagraphListByTextRange(selection)

    paragraphs.fastForEach { paragraph ->
        removeOrderedList(paragraph)
    }
}

/**
 * Increase the level of the current selected lists.
 *
 * If the current selection is not a list, this method does nothing.
 *
 * If multiple paragraphs are selected, they all must be lists.
 */
 fun RichTextState.increaseListLevel() {
    if (!isList)
        return

    val paragraphs = getRichParagraphListByTextRange(selection)

    if (paragraphs.isEmpty())
        return

    if (!canIncreaseListLevel(paragraphs))
        return

    // Increase list level
    val levelNumberMap = mutableMapOf<Int, Int>()
    var minParagraphLevel = Int.MAX_VALUE
    var minParagraphLevelOrderedListNumber = -1
    var startParagraphIndex = -1
    var startParagraphLevel = -1
    var endParagraphIndex = -1
    var processedParagraphCount = 0

    val firstSelectedParagraph = paragraphs.first()

    for (i in richParagraphList.indices) {
        val paragraph = richParagraphList[i]
        val type = paragraph.type

        // Skip paragraphs before the selected paragraphs
        if (startParagraphIndex == -1) {
            if (paragraph == firstSelectedParagraph) {
                startParagraphIndex = i
                startParagraphLevel =
                    if (type is ConfigurableListLevel)
                        type.level
                    else
                        0
            } else {
                if (type is ConfigurableListLevel) {
                    levelNumberMap.keys.toList().fastForEach { level ->
                        if (level > type.level)
                            levelNumberMap.remove(level)
                    }

                    if (type is OrderedList)
                        levelNumberMap[type.level] = type.number

                    if (type is UnorderedList)
                        levelNumberMap.remove(type.level)
                } else {
                    levelNumberMap.clear()
                }

                continue
            }
        }

        if (processedParagraphCount >= paragraphs.size) {
            if (
                type !is ConfigurableListLevel ||
                type.level <= minParagraphLevel
            ) {
                endParagraphIndex = i - 1
                break
            }
        }

        if (type is ConfigurableListLevel) {
            if (type.level <= minParagraphLevel) {
                minParagraphLevel = type.level
                minParagraphLevelOrderedListNumber =
                    if (type is OrderedList)
                        type.number - 1
                    else
                        -1
            }

            type.level++
        } else {
            if (minParagraphLevel != Int.MAX_VALUE && minParagraphLevelOrderedListNumber != -1)
                levelNumberMap[minParagraphLevel] = minParagraphLevelOrderedListNumber

            minParagraphLevel = Int.MAX_VALUE
            minParagraphLevelOrderedListNumber = -1
        }

        processedParagraphCount++
    }

    if (minParagraphLevel != Int.MAX_VALUE && minParagraphLevelOrderedListNumber != -1)
        levelNumberMap[minParagraphLevel] = minParagraphLevelOrderedListNumber

    // Adjust ordered list numbers
    val newTextFieldValue = adjustOrderedListsNumbers(
        startParagraphIndex = startParagraphIndex,
        startNumber = levelNumberMap[startParagraphLevel + 1]?.plus(1) ?: 1,
        textFieldValue = textFieldValue,
        initialLevelNumberMap = levelNumberMap,
    )

    updateTextFieldValue(
        newTextFieldValue = newTextFieldValue,
    )
}

/**
 * Decrease the level of the current selected lists.
 *
 * If the current selection is not a list, this method does nothing.
 *
 * If multiple paragraphs are selected, they all must be lists.
 */
 fun RichTextState.decreaseListLevel() {
    if (!isList)
        return

    val paragraphs = getRichParagraphListByTextRange(selection)

    if (paragraphs.isEmpty())
        return

    if (!canDecreaseListLevel(paragraphs))
        return

    // Decrease list level
    val levelNumberMap = mutableMapOf<Int, Int>()
    var minParagraphLevel = Int.MAX_VALUE
    var minParagraphLevelOrderedListNumber = -1
    var startParagraphIndex = -1
    var endParagraphIndex = -1
    var startParagraphLevel = -1
    var processedParagraphCount = 0

    val firstSelectedParagraph = paragraphs.first()

    for (i in richParagraphList.indices) {
        val paragraph = richParagraphList[i]
        val type = paragraph.type

        // Skip paragraphs before the selected paragraphs
        if (startParagraphIndex == -1) {
            if (paragraph == firstSelectedParagraph) {
                startParagraphIndex = i
                startParagraphLevel =
                    if (type is ConfigurableListLevel)
                        type.level
                    else
                        0
            } else {
                if (type is ConfigurableListLevel) {
                    levelNumberMap.keys.toList().fastForEach { level ->
                        if (level > type.level)
                            levelNumberMap.remove(level)
                    }

                    if (type is OrderedList)
                        levelNumberMap[type.level] = type.number

                    if (type is UnorderedList)
                        levelNumberMap.remove(type.level)
                } else {
                    levelNumberMap.clear()
                }

                continue
            }
        }

        if (processedParagraphCount >= paragraphs.size) {
            if (
                type !is ConfigurableListLevel ||
                type.level <= minParagraphLevel
            ) {
                endParagraphIndex = i - 1
                break
            }
        }

        if (type is ConfigurableListLevel) {
            if (type.level <= minParagraphLevel) {
                minParagraphLevel = type.level
                minParagraphLevelOrderedListNumber =
                    if (type is OrderedList)
                        type.number - 1
                    else
                        -1
            }

            type.level = (type.level - 1).coerceAtLeast(1)
        } else {
            minParagraphLevel = Int.MAX_VALUE
            minParagraphLevelOrderedListNumber = -1
        }

        processedParagraphCount++
    }

    // Adjust ordered list numbers
    val newTextFieldValue = adjustOrderedListsNumbers(
        startParagraphIndex = startParagraphIndex,
        startNumber = levelNumberMap[startParagraphLevel - 1]?.plus(1) ?: 1,
        textFieldValue = textFieldValue,
        initialLevelNumberMap = levelNumberMap,
    )

    updateTextFieldValue(
        newTextFieldValue = newTextFieldValue,
    )
}


private fun RichTextState.addUnorderedList(paragraph: RichParagraph) {
    val paragraphType = paragraph.type
    if (paragraphType is UnorderedList)
        return

    val index = richParagraphList.indexOf(paragraph)

    if (index == -1)
        return

    val listLevel =
        if (paragraphType is ConfigurableListLevel)
            paragraphType.level
        else
            1

    val newType = UnorderedList(
        config = config,
        initialLevel = listLevel,
    )

    val newTextFieldValue = adjustOrderedListsNumbers(
        startParagraphIndex = index,
        startNumber = 1,
        textFieldValue = updateParagraphType(
            paragraph = paragraph,
            newType = newType,
            textFieldValue = textFieldValue,
        ),
    )

    updateTextFieldValue(
        newTextFieldValue = newTextFieldValue
    )
}

private fun RichTextState.removeUnorderedList(paragraph: RichParagraph) {
    if (paragraph.type !is UnorderedList)
        return

    resetParagraphType(paragraph = paragraph)
}

private fun RichTextState.addOrderedList(paragraph: RichParagraph) {
    val paragraphType = paragraph.type

    if (paragraphType is OrderedList)
        return

    val index = richParagraphList.indexOf(paragraph)

    if (index == -1)
        return

    var orderedListNumber = 1

    val listLevel =
        if (paragraphType is ConfigurableListLevel)
            paragraphType.level
        else
            1

    for (i in index - 1 downTo 0) {
        val prevParagraph = richParagraphList[i]
        val prevParagraphType = prevParagraph.type

        if (prevParagraphType is ConfigurableListLevel && prevParagraphType.level < listLevel)
            break

        if (prevParagraphType is ConfigurableListLevel && prevParagraphType !is OrderedList)
            continue

        if (prevParagraphType !is OrderedList)
            break

        if (prevParagraphType.level > listLevel)
            continue

        orderedListNumber = prevParagraphType.number + 1

        break
    }

    val newType = OrderedList(
        number = orderedListNumber,
        config = config,
        initialLevel = listLevel,
    )

    val newTextFieldValue = adjustOrderedListsNumbers(
        startParagraphIndex = index,
        startNumber = orderedListNumber,
        textFieldValue = updateParagraphType(
            paragraph = paragraph,
            newType = newType,
            textFieldValue = textFieldValue,
        ),
    )

    updateTextFieldValue(
        newTextFieldValue = newTextFieldValue,
    )
}

private fun RichTextState.removeOrderedList(paragraph: RichParagraph) {
    if (paragraph.type !is OrderedList) return
    val index = richParagraphList.indexOf(paragraph)
    if (index == -1) return

    for (i in (index + 1)..richParagraphList.lastIndex) {
        val currentParagraphType = richParagraphList[i].type
        if (currentParagraphType !is OrderedList) break
        currentParagraphType.number = i - index
    }

    resetParagraphType(paragraph = paragraph)
}


internal fun RichTextState.checkListStart(richSpan: RichSpan) {
    if (richSpan.paragraph.type !is DefaultParagraphType)
        return

    if (!richSpan.isFirstInParagraph)
        return

    if (richSpan.text == "- " || richSpan.text == "* ") {
        richSpan.paragraph.type = UnorderedList(
            config = config,
        )
        richSpan.text = ""
    } else if (richSpan.text.matches(Regex("^\\d+\\. "))) {
        val dotIndex = richSpan.text.indexOf('.')
        if (dotIndex != -1) {
            val number = richSpan.text.substring(0, dotIndex).toIntOrNull() ?: 1
            richSpan.paragraph.type = OrderedList(
                number = number,
                config = config,
            )
            richSpan.text = ""
        }
    }
}

/**
 * Checks the ordered lists numbers and adjusts them if needed.
 *
 * @param startParagraphIndex the start paragraph index to start checking from.
 * @param startNumber the start number to start from.
 * @param textFieldValue the text field value to update.
 * @return the updated text field value.
 */
internal fun RichTextState.adjustOrderedListsNumbers(
    startParagraphIndex: Int,
    startNumber: Int,
    textFieldValue: TextFieldValue,
    initialLevelNumberMap: Map<Int, Int> = emptyMap(),
): TextFieldValue {
    var newTextFieldValue = textFieldValue
    // The map to store the list number of each list level, level -> number
    val levelNumberMap = mutableMapOf<Int, Int>()
    levelNumberMap.putAll(initialLevelNumberMap)

    // Update the paragraph type of the paragraphs after the new paragraph
    for (i in (startParagraphIndex)..(richParagraphList.lastIndex)) {
        val currentParagraph = richParagraphList[i]
        val currentParagraphType = currentParagraph.type

        if (currentParagraphType !is ConfigurableListLevel)
            break

        levelNumberMap.keys.toList().fastForEach { level ->
            if (level > currentParagraphType.level)
                levelNumberMap.remove(level)
        }

        if (currentParagraphType is UnorderedList) {
            levelNumberMap[currentParagraphType.level] = 0
            continue
        }

        if (currentParagraphType !is OrderedList)
            break

        val currentNumber =
            if (i == startParagraphIndex)
                startNumber
            else
                levelNumberMap[currentParagraphType.level]
                    ?.plus(1)
                    ?: run {
                        if (levelNumberMap.containsKey(currentParagraphType.level - 1))
                            1
                        else
                            currentParagraphType.number
                    }

        levelNumberMap[currentParagraphType.level] = currentNumber

        newTextFieldValue = updateParagraphType(
            paragraph = currentParagraph,
            newType = OrderedList(
                number = currentNumber,
                config = config,
                startTextWidth = currentParagraphType.startTextWidth,
                initialLevel = currentParagraphType.level
            ),
            textFieldValue = newTextFieldValue,
        )
    }

    return newTextFieldValue
}

internal fun RichTextState.checkOrderedListsNumbers(
    startParagraphIndex: Int,
    endParagraphIndex: Int,
) {
    // The map to store the list number of each list level, level -> number
    val levelNumberMap = mutableMapOf<Int, Int>()
    val startParagraph = richParagraphList.getOrNull(startParagraphIndex)
    val startParagraphType = startParagraph?.type
    if (startParagraphType is OrderedList)
        levelNumberMap[startParagraphType.level] = startParagraphType.number

    // Update the paragraph type of the paragraphs after the new paragraph
    for (i in (startParagraphIndex + 1)..richParagraphList.lastIndex) {
        val currentParagraph = richParagraphList[i]
        val currentParagraphType = currentParagraph.type

        if (currentParagraphType is ConfigurableListLevel) {
            // Clear the completed list levels
            levelNumberMap.keys.toList().fastForEach { level ->
                if (level > currentParagraphType.level)
                    levelNumberMap.remove(level)
            }
        } else {
            // Clear the map if the current paragraph is not a list
            levelNumberMap.clear()
        }

        // Remove current list level from map if the current paragraph is an unordered list
        if (currentParagraphType is UnorderedList)
            levelNumberMap.remove(currentParagraphType.level)

        if (currentParagraphType is OrderedList) {
            val number =
                levelNumberMap[currentParagraphType.level]
                    ?.plus(1)
                    ?: currentParagraphType.number

            levelNumberMap[currentParagraphType.level] = number

            tempTextFieldValue = updateParagraphType(
                paragraph = currentParagraph,
                newType = OrderedList(
                    number = number,
                    config = config,
                    startTextWidth = currentParagraphType.startTextWidth,
                    initialLevel = currentParagraphType.level
                ),
                textFieldValue = tempTextFieldValue,
            )
        }

        // Break if we reach the end paragraph index
        if (i >= endParagraphIndex)
            break
    }
}

/**
 * Checks weather the list level can be increased or not.
 *
 * @param paragraphs the list of paragraphs to check.
 * @return true if the list level can be increased, false otherwise.
 */
internal fun RichTextState.canIncreaseListLevel(
    paragraphs: List<RichParagraph> = getRichParagraphListByTextRange(selection),
): Boolean {
    if (paragraphs.isEmpty())
        return false

    val firstParagraph = paragraphs.first()
    val firstParagraphType = firstParagraph.type
    val firstParagraphIndex = richParagraphList.indexOf(firstParagraph)

    if (firstParagraphIndex == -1 || firstParagraphType !is ConfigurableListLevel)
        return false

    val previousParagraph = richParagraphList.getOrNull(firstParagraphIndex - 1)
    val previousParagraphType = previousParagraph?.type

    // The previous paragraph must be a list, otherwise we can't increase the list level
    if (previousParagraph == null || previousParagraphType !is ConfigurableListLevel)
        return false

    // The first paragraph must have the same or lower list level than the previous one
    if (firstParagraphType.level > previousParagraphType.level)
        return false

    paragraphs.fastForEach { paragraph ->
        val paragraphType = paragraph.type

        // All paragraphs must be ConfigurableListLevel
        if (paragraphType !is ConfigurableListLevel)
            return false

        // TODO: Maybe in the future we can remove this condition
        // The paragraph must have the same or higher list level than the first paragraph
        if (paragraphType.level < firstParagraphType.level)
            return false
    }

    return true
}

/**
 * Checks weather the list level can be decreased or not.
 *
 * @param paragraphs the list of paragraphs to check.
 * @return true if the list level can be decreased, false otherwise.
 */
internal fun RichTextState.canDecreaseListLevel(
    paragraphs: List<RichParagraph> = getRichParagraphListByTextRange(selection),
): Boolean {
    if (paragraphs.isEmpty())
        return false

    paragraphs.fastForEach { paragraph ->
        val paragraphType = paragraph.type

        // All paragraphs must be ConfigurableListLevel
        if (paragraphType !is ConfigurableListLevel)
            return false

        // The paragraph list level must be at least 2
        if (paragraphType.level < 2)
            return false
    }

    return true
}
